-module(kenken).

-export([load/1, solve/1, test/0]).

-record(cell, {setid, x, y, value}).
-record(set, {id, shape, operation, value}).
-record(board, {todo, dim, cells, sets}).

load(File) ->
    {ok, Data} = file:read_file(File),
    Text = binary_to_list(Data),
    Lines = string:tokens(Text, "\n"),
    [First | _ ] = Lines,
    N = erlang:length(First),
    {Grid, Rules} = lists:split(N, Lines),
    Values = lists:seq(1, N),
    Cells = [#cell{setid=Chr, value=Values, x=Pos rem N, y=Pos div N} || 
		{Pos, Chr} <- lists:zip(
				lists:seq(0, N*N-1), 
				lists:flatten(Grid))],
    Sets = [mkset(Rule) || Rule <- Rules],
    Board = todo(#board{dim=N, cells=Cells, sets=Sets}).

todo(#board{todo=Todo, cells=Cells} = Board) ->
    NewTodo = length([X || X <- Cells, is_list(X#cell.value)]),
    if NewTodo == Todo ->
	    Board;
       true -> %% else
	    Board#board{todo=NewTodo}
    end.

mkset(Rule) ->
    [Char, $ | Val] = Rule,
    [Op | RVal ] =  lists:reverse(Val),
    case Op of 
	Op when Op >= $0, Op =< $9 ->
	    #set{id=Char, operation=$=, value=list_to_integer(Val)};
	Op when Op==$+; Op==$-; Op==$x; Op==$/ ->
	    #set{id=Char, operation=Op, 
		 value=list_to_integer(lists:reverse(RVal))}
    end.
	 
solve(Puzzle) ->
    Rules = [fun solve_exact/1],
    Next = todo(lists:foldl(fun(F, B) -> F(B) end, Puzzle, Rules)),
    case Next of 
	#board{todo=0} ->
	    {solved, Next};
	Puzzle ->
	    {unsolved, Puzzle};
	_ ->
	    solve(Next)
    end.

test() ->
    Board = load("sample2"),
    Output = solve(Board),
    case Output of 
	{solved, B} ->
	    print_board(B);
	{unsolved, B} ->
	    print_board(B);
	_ ->
	    io:format("~p~n~p~n", [Board, Output])
    end,
    ok.

print_board(B) ->
    io:format("~s~n", [
    lists:flatten(
      lists:map(
	fun(C) ->
		V = case C#cell.value of 
			N when is_integer(N) ->
			    integer_to_list(N);
			_ -> C#cell.setid
		    end,
		case {C#cell.x, C#cell.y} of
		    {0, 0} ->
			V;
		    {0, _} ->
			["\n", V];
		    _ ->
			V
		end
	end,
	B#board.cells))]).
			
			    
%% end of shared code.  After this comes individual solvers

set_value(Board, X, Y, Value) ->
    Existing = lists:nth(1+ X + Y * Board#board.dim, Board#board.cells),
    case Existing#cell.value of
	Value ->
	    Board;
	_ -> 
	    Board#board{cells=
			[
			 case C of 
			     #cell{x=X, y=Y} ->
				 C#cell{value=Value};
			     _C ->
				 _C
			 end || C <- Board#board.cells
			]}
    end.

solve_exact(Board) ->
    lists:foldl(fun solve_exact/2, Board,
		  [S || S <- Board#board.sets, S#set.operation==$=]).
solve_exact(S, B) ->
    [C] = [X || X <- B#board.cells, X#cell.setid == S#set.id],
    set_value(B, C#cell.x, C#cell.y, S#set.value).
